#include <sdk.h>
#include "systemheadersthread.h"

#define CC_SYSTEMHEADERSTHREAD_DEBUG_OUTPUT 0

long idSystemHeadersThreadFinish = wxNewId();
long idSystemHeadersThreadUpdate = wxNewId();
long idSystemHeadersThreadError  = wxNewId();

class HeaderDirTraverser : public wxDirTraverser {
  public:
    HeaderDirTraverser( wxThread* thread, wxCriticalSection* critSect,
                        SystemHeadersMap& headersMap, const wxString& searchDir );
    virtual ~HeaderDirTraverser();
    virtual wxDirTraverseResult OnFile( const wxString& filename );
    virtual wxDirTraverseResult OnDir( const wxString& dirname );
  private:
    void AddLock( bool is_file );
    wxDirTraverseResult GetStatus( const wxString &path );
  private:
    wxThread* m_Thread;
    wxCriticalSection* m_SystemHeadersThreadCS;
    const SystemHeadersMap& m_SystemHeadersMap;
    std::set<wxString> m_VisitedDirs;
    const wxString& m_SearchDir;
    StringSet& m_Headers;
    bool m_Locked;
    size_t m_Dirs;
    size_t m_Files;
};

SystemHeadersThread::SystemHeadersThread( wxEvtHandler* parent,
    wxCriticalSection* critSect,
    SystemHeadersMap& headersMap,
    const wxArrayString& incDirs ) :
  wxThread( wxTHREAD_JOINABLE ),
  m_Parent( parent ),
  m_SystemHeadersThreadCS( critSect ),
  m_SystemHeadersMap( headersMap ),
  m_IncludeDirs( incDirs ) {
  Create();
  SetPriority( 60u );
}

SystemHeadersThread::~SystemHeadersThread() {
}

void* SystemHeadersThread::Entry() {
  wxArrayString dirs;
  {
    wxCriticalSectionLocker locker( *m_SystemHeadersThreadCS );
    for( size_t i = 0; i < m_IncludeDirs.GetCount(); ++i ) {
      if( m_SystemHeadersMap.find( m_IncludeDirs[i] ) == m_SystemHeadersMap.end() ) {
        dirs.Add( m_IncludeDirs[i] );
        m_SystemHeadersMap[m_IncludeDirs[i]] = StringSet();
      }
    }
  }
  for( size_t i = 0; i < dirs.GetCount(); ++i ) {
    if( TestDestroy() ) {
      break;
    }
    wxDir dir( dirs[i] );
    if( !dir.IsOpened() ) {
      CodeBlocksThreadEvent evt( wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadError );
      evt.SetClientData( this );
      evt.SetString( F( _T( "SystemHeadersThread: Unable to open: %s" ), dirs[i].wx_str() ) );
      wxPostEvent( m_Parent, evt );
      continue;
    }
    HeaderDirTraverser traverser( this, m_SystemHeadersThreadCS, m_SystemHeadersMap, dirs[i] );
    dir.Traverse( traverser, wxEmptyString, wxDIR_FILES | wxDIR_DIRS );
    if( TestDestroy() ) {
      break;
    }
    CodeBlocksThreadEvent evt( wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadUpdate );
    evt.SetClientData( this );
    evt.SetString( F( _T( "SystemHeadersThread: %s , %lu" ), dirs[i].wx_str(), static_cast<unsigned long>( m_SystemHeadersMap[dirs[i]].size() ) ) );
    wxPostEvent( m_Parent, evt );
  }
  if( !TestDestroy() ) {
    CodeBlocksThreadEvent evt( wxEVT_COMMAND_MENU_SELECTED, idSystemHeadersThreadFinish );
    evt.SetClientData( this );
    if( !dirs.IsEmpty() ) {
      evt.SetString( F( _T( "SystemHeadersThread: Total number of paths: %lu" ), static_cast<unsigned long>( dirs.GetCount() ) ) );
    }
    wxPostEvent( m_Parent, evt );
  }
  return NULL;
}


HeaderDirTraverser::HeaderDirTraverser( wxThread*          thread,
                                        wxCriticalSection* critSect,
                                        SystemHeadersMap&  headersMap,
                                        const              wxString& searchDir ) :
  m_Thread( thread ),
  m_SystemHeadersThreadCS( critSect ),
  m_SystemHeadersMap( headersMap ),
  m_SearchDir( searchDir ),
  m_Headers( headersMap[searchDir] ),
  m_Locked( false ),
  m_Dirs( 0 ),
  m_Files( 0 ) {
}

HeaderDirTraverser::~HeaderDirTraverser() {
  if( m_Locked ) {
    m_SystemHeadersThreadCS->Leave();
  }
}

wxDirTraverseResult HeaderDirTraverser::OnFile( const wxString& filename ) {
  if( m_Thread->TestDestroy() ) {
    return wxDIR_STOP;
  }
  AddLock( true );
  wxFileName fn( filename );
  if( !fn.HasExt() || fn.GetExt().GetChar( 0 ) == _T( 'h' ) ) {
    fn.MakeRelativeTo( m_SearchDir );
    wxString header( fn.GetFullPath() );
    header.Replace( _T( "\\" ), _T( "/" ), true );
    m_Headers.insert( header );
  }
  return wxDIR_CONTINUE;
}

wxDirTraverseResult HeaderDirTraverser::OnDir( const wxString& dirname ) {
  if( m_Thread->TestDestroy() ) {
    return wxDIR_STOP;
  }
  AddLock( false );
  wxString path = cbResolveSymLinkedDirPathRecursive( dirname );
  if( path.Last() != _T( '/' ) ) {
    path.Append( _T( '/' ) );
  }
  return GetStatus( path );
}

void HeaderDirTraverser::AddLock( bool is_file ) {
  if( is_file ) {
    m_Files++;
  } else {
    m_Dirs++;
  }
  if( ( m_Files + m_Dirs ) % 100 == 1 ) {
    if( m_Locked ) {
      m_SystemHeadersThreadCS->Leave();
      m_Locked = false;
    }
    m_SystemHeadersThreadCS->Enter();
    m_Locked = true;
  }
}

wxDirTraverseResult HeaderDirTraverser::GetStatus( const wxString &path ) {
  if( m_SystemHeadersMap.find( path ) != m_SystemHeadersMap.end() ) {
    return wxDIR_IGNORE;
  }
  if( m_VisitedDirs.find( path ) != m_VisitedDirs.end() ) {
    return wxDIR_IGNORE;
  }
  m_VisitedDirs.insert( path );
  return wxDIR_CONTINUE;
}
